package client;

import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.*;
import java.util.*;

import javax.swing.*;
import javax.swing.event.*;
import common.*;

//This is the round window.

public class SwingRound extends JFrame implements Observer {
	private static final long serialVersionUID = -8617873731427952555L;
	private static final Font courier = new Font("Courier",Font.PLAIN,12);
	private final ClientGameState cgs;
	private final MessageSender m;
	private final static int pixelAdjust = 70;
	
	private final String [] Sreg = {"The Round will start shortly.",""};
	private final String [] Sover = {"The Round is over.","View the Lobby for Results"};
	private String [] curr;
	private final List<JList> Alists = new ArrayList<JList>();
	private JList Alist;
	
	private final String [] comods = {"Loading Commodities..."};
	private final JComboBox combobox = new JComboBox(comods);
	
	private final JTextArea area = new JTextArea(
			"Instructions : \n" +
			"Right before the trading phase begins, you will be assigned quotas and price limits which will appear here.\n\n" +
			"Commodities will be able to be selected from the drop-down menu below.\n\n" +
			"Bids and Asks for this commodity will then appear on the window on the left. Those marked with an asterisk (*) are your own bids and can be cancelled by selecting them and hitting 'Cancel'.\n" +
			"A 'Bid' or 'Ask' button will appear when the round begins, pressing it will propose a match to the current selected Bid or Ask or can be used to create a new Bid/Ask."+
			"\n\nThe round will begin after all players are ready.");
	
	private final JButton bidask = new JButton("Ready"); // new JButton("Bid/Ask");
	private final JButton cancel = new JButton("Cancel");
	
	private final List<JScrollPane> pane = new ArrayList<JScrollPane>();
	private final JTabbedPane tabs = new JTabbedPane(JTabbedPane.TOP,JTabbedPane.WRAP_TAB_LAYOUT);
	private enum TabStat { TAB_ALL, TAB_BID, TAB_ASK, TAB_MINE, TAB_TRADE, TAB_SCORE};
	private TabStat current = TabStat.TAB_ALL;
	
	private Auction [] auctions;
	private Auction [] myA;
	private Auction selected;
	
	protected SwingBidAsk win = null;
	
	public SwingRound (Observer o, ClientGameState cgs) {
		m = new MessageSender(o);
		this.cgs = cgs;
		
		
		JPanel jp = new JPanel();
		jp.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
		jp.setLayout(new GridLayout(1,2,1,1));
		
		// Split the screen into left and right.
		JPanel left = new JPanel();jp.add(left);
		left.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
		JPanel right = new JPanel();jp.add(right);
		right.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
		
		//Left
		JPanel leftbot = new JPanel();		
		left.setLayout(new BoxLayout(left, BoxLayout.Y_AXIS));
		left.add(tabs);
		left.add(Box.createRigidArea(new Dimension(0, 15)));
		left.add(leftbot);
	
		//Add the tabs and various panes.
		int N = TabStat.values().length;
		while (N-->0) { 
			JScrollPane p = new JScrollPane();
			final JList Alist2 = new JList();
			Alist2.setPreferredSize(new Dimension(300+pixelAdjust,300));
			Alist2.setValueIsAdjusting(true);
			Alist2.addListSelectionListener(new ListSelectionListener() {
				public void valueChanged(ListSelectionEvent e) {
					int in = Alist2.getSelectedIndex();
					if (in < 0 || auctions == null || in >= auctions.length) {
						selected = null;
					} else {
						selected = auctions[in];
					}
				}
			});
			Alist2.setFont(courier);
			p.getViewport().add(Alist2);
			p.setPreferredSize(new Dimension(310+pixelAdjust,310));
			pane.add(p);
			Alists.add(Alist2);
		}
		tabs.add("All", pane.get(0));
		tabs.add("Bids", pane.get(1));
		tabs.add("Asks", pane.get(2));
		tabs.add("My Auctions", pane.get(3));
		tabs.add("Trades", pane.get(4));
		tabs.add("Scores", pane.get(5));
		//Tab switch
		tabs.getModel().addChangeListener(new ChangeListener() {
			public void stateChanged(ChangeEvent e) {
				current = TabStat.values()[tabs.getModel().getSelectedIndex()];
				updateThings();
			}
		});
		updateAList();
		
		//Left Bottom
		leftbot.setLayout(new BoxLayout(leftbot,BoxLayout.X_AXIS));
		leftbot.add(bidask);
		leftbot.add(Box.createRigidArea(new Dimension(15, 0)));
		leftbot.add(cancel);
		
		//Bid/Ask/Ready button
		bidask.addActionListener(new ActionListener() {
        	public void actionPerformed(ActionEvent e){
        		if (bidask.getText().contains("Ready"))
        			sendReady();
        		else
        			bidAskWindow();
			}
        });
		//Cancel button.
		cancel.addActionListener(new ActionListener() {
        	public void actionPerformed(ActionEvent e){
				sendCancel();
			}
        });
		
		//Right
		right.setLayout(new BoxLayout(right, BoxLayout.Y_AXIS));
		
		area.setLineWrap(true);
        area.setWrapStyleWord(true);
        area.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
        area.setEditable(false);
		
        combobox.setSelectedIndex(0);
        combobox.setPreferredSize(new Dimension(240, 22));
        combobox.setMaximumSize(new Dimension(240, 22));
        combobox.addItemListener(new ItemListener() {
        	public void itemStateChanged( ItemEvent ie) {
                if (ie.getStateChange() == ItemEvent.SELECTED) {
                    JComboBox combo = (JComboBox) ie.getSource();
                    int index = combo.getSelectedIndex();
                    Commodity [] c = Commodity.values();
                    showAreaStats(c[index]);
                    updateAList();
                    closeBidAsk();
                }
        	}
        });
        right.add(area);
        right.add(Box.createRigidArea(new Dimension(0, 15)));
        right.add(combobox);
        updateComboBox();
		
		
		jp.add(left);
		jp.add(right);
		this.add(jp);
		this.pack();
		this.validate();
		cgs.addObserver(this);
		this.setDefaultCloseOperation ( JFrame.DISPOSE_ON_CLOSE );
		if (cgs.getCurrentRound() != null)
			this.setTitle("Round " + cgs.getCurrentRound().getID());
		else
			this.setTitle("Round Window");
		
		updateThings();
	}
	//This method updates based upon the phase.
	private void updateThings() {
		if (cgs.getPlayer() != null)
			if (currentRoundPhase() == PhaseEnum.REGISTRATION)
				registerUpdate();
			else
				statusUpdate();
	}
	//Updates the auction list.
	private void setAList() {
		int i = tabs.getSelectedIndex();
		Alist = Alists.get((i < 0 || i >= Alists.size()) ? 0 : i);
	}
	
	// Updates the AuctionList and adjust the scroll-bar length
	// accordingly.
	private void updateAList() {
		setAList();
		PhaseEnum e = getCurrentPhase();
		if (e == PhaseEnum.REGISTRATION) {
			if (curr != Sreg) Alist.setListData(Sreg);
			curr = Sreg;
		} else if (e == PhaseEnum.RESULTS) {
			Alist.setListData(Sover);
			curr = Sover;
		} else {
			Commodity c = selectedCommodity();
			String [] s = 	  (current == TabStat.TAB_TRADE) 
							? displayTradeList(c) 
							: (current == TabStat.TAB_SCORE) 
							? displayScores(c)
							: displayAuctionList(c);
			setPreferredSize(s.length);
			
			curr = s;	
		}		
	}
	//Sets the scrollbar size to adjust
	//when the number of auctions/whatever is displayed
	//changes.
	private void setPreferredSize(int i) {
		Alist.setPreferredSize(new Dimension(280+pixelAdjust,i*20));
	}
	
	// Queries the gamestate for various auciton information.
	// depending upon the current selected tab.
	private Auction[] getAuctions(TabStat t, Commodity c) {
		if (t == TabStat.TAB_ALL) {
			return cgs.getAllAuctions(c);
		} else if (t == TabStat.TAB_BID) {
			return cgs.getAllBids(c);
		} else if (t == TabStat.TAB_ASK) {
			return cgs.getAllAsks(c);	
		} else if (t == TabStat.TAB_MINE) {
			return cgs.getMyAuctions(c);
		}
		return new Auction[0];
	}
	
	// Displays a (sorted) list of players
	// and their scores which is updated in realtime.
	private String [] displayScores(Commodity c) {
		if (cgs.getCurrentRound() == null) return new String[0]; 
		List<Player> p = cgs.getCurrentRound().getPlayers();
		Collections.sort(p);
		String [] s = new String [p.size()];
		for (int i = 0; i<p.size(); ++i) { 
			s[i] = p.get(i).getName() + " : " + String.format("%.2f", p.get(i).getScore());
			if (cgs.getPlayer() != null && p.get(i).equals(cgs.getPlayer())) s[i] = "*" + s[i];
		}
		s = AllTheEvilsOfThisWorld.stringArrayToStringArray(s);
		Alist.setListData(s);
		return s;
	}
	
	// Displays a list of all trades for this commodity in reverse
	// chronological order (newest first)
	private String [] displayTradeList(Commodity c) {
		AuctionMatch [] m = cgs.getTrades(c);
		String [] s = new String [m.length];
		for (int i = 0; i<m.length; ++i) { 
			s[i] = m[i].toString();
			Player p = cgs.getPlayer();
			if  (p != null && (p.ownsAuction(m[i].original) || p.ownsAuction(m[i].matchedWith)))
				s[i] = "*" + s[i];
		}
		Alist.setListData(s);
		return s;
	}
	// Returns a string with the list of auctions
	// for the current round.
	// Depending on the selected tab this list could be different.
	private String [] displayAuctionList(Commodity c) {
		auctions = getAuctions(current,c);
		Arrays.sort(auctions);
		boolean isBuyer = !(bidask.getText().contains("Ask"));
		if (isBuyer) {
			Auction [] a = new Auction [auctions.length];
			for (int i = 0; i<a.length; ++i) a[i] = auctions[a.length-i-1];
			auctions = a;
		}
		myA = cgs.getMyAuctions(c);
		
		String [] s = new String [auctions.length];
		int index = -1;
		for (int i = 0; i<auctions.length; ++i) {
			Auction A = auctions[i];
			s[i] = A.toString();
			for (int j = 0; j<myA.length; ++j) {
				if (myA[j].equals(A)) {
					s[i] = "*" + s[i];
				}
			}
			if (selected != null && A.equals(selected)) 
				index = i;	
		}
		Alist.setListData(s);
		if (index >=0) 
			Alist.setSelectedIndex(index);
		
		return s;
	}
	//Shows the stats for the current commodity in area.
	private Commodity selectedCommodity() { 
		return Commodity.values()[combobox.getSelectedIndex()];
	}
	//Gives the phase of the current round
	private PhaseEnum getCurrentPhase() { return currentRoundPhase();}
	private PhaseEnum currentRoundPhase() {
		return (cgs.getCurrentRound() != null) ? cgs.getCurrentRound().getCurrentPhase() : PhaseEnum.REGISTRATION;
	}
	
	//Shows the stats for each commodity
	//in the right hand window.
	private void showAreaStats() {
		showAreaStats(Commodity.values()[combobox.getSelectedIndex()]);
	}
	private void showAreaStats(Commodity c) {
		if (cgs.getPlayer() == null) return;
		Player p = cgs.getPlayer();
		CommodityLimits L = p.getLimits(c);
		if (L == null) return;
		String s = "";
		s = s+  "Your Score : \t" + String.format("%.2f",p.getScore()) + "\n";
		s = s+ 	"Commodity :\t" + c.toString() + "\n";
		s = s+	"Task :\t" + ((L.isBuyer) ? "Buy" : "Sell") + "\n";
		s = s+	"Quota :\t" + (L.quota - p.getSold(c) - p.getMaybeSold(c)) + " Units\n";
		String bound = String.format("%.2f", L.bound);
		s = s+  "Minimum Price : \t$" + ((L.isBuyer)?0:bound) + "\n";
		s = s+  "Maximum Price : \t$" + ((L.isBuyer)?bound:"Infinity") + "\n"; 
		s = s+ 	"Your Active " + ((L.isBuyer)?"Bids":"Asks")+" :\t" + p.getMaybeSold(c) + " Units\n";
		s = s+ ((L.isBuyer) ? "Units Bought : \t" : "Units Sold : \t") + p.getSold(c); 
		//TODO : More stuff
		area.setText(s);
		
		bidask.setText((L.isBuyer)?"Bid":"Ask");
	}
	
	//Updates the combo box.
	private void updateComboBox() {
		updateComboBox(Commodity.values());
	}
	private void updateComboBox(Commodity [] a) {
		int j = combobox.getSelectedIndex();
		combobox.removeAllItems();
		for (int i = 0; i<a.length; ++i) {
			combobox.addItem(a[i].toString());
			if (i == j)
				combobox.setSelectedIndex(i);
		}
	}
	
	// Called during trading phase
	// This method updates this window as required.
	private void statusUpdate() {
		//Store the bid-window focus.
		boolean bidWindowFocus = (win == null) ? false : win.isFocused();
		if (win != null) win.storeFocus();
		
		//Update the list of auctions.
		updateAList();
		//Update the commodity stats (eg Quota, etc).
		showAreaStats();
		if (cgs.getCurrentRound() != null && cgs.getCurrentRound().getCurrentPhase() == PhaseEnum.RESULTS)
			closeBidAsk();
		
		//Refocus the bid window if it was focused originally.
		if (bidWindowFocus && win != null) win.requestFocus();
		
		this.validate();
		this.repaint();
	}
	
	// Updates the state of all players based upon
	// their ready state.
	private void updateReadyPlayers() {
		setAList();
		List<Player> LP = cgs.getCurrentPlayers();
		if (LP == null) return;
		
		String [] s = new String [LP.size()];
		for (int i = 0; i<s.length; ++i) {
			s[i] = LP.get(i).getName() + "  \t:\t  " + ((LP.get(i).isReady()) ? "READY" : "NOT READY");
			if (cgs.getPlayer() != null && LP.get(i).equals(cgs.getPlayer())) s[i] = "*" + s[i];
		}
		s = AllTheEvilsOfThisWorld.stringArrayToStringArray(s);
		setPreferredSize(s.length);
		Alist.setListData(s);
	}
	
	// Updates the state of the ready button based upon the
	// player's ready state.
	public void updateReadyButton() {
		Player p = cgs.getPlayer();
		if (p == null) return;
		bidask.setText((p.isReady()) ? "UnReady" : "Ready");
	}
	
	// Called during register phase of the current round.
	private void registerUpdate() {
		updateReadyPlayers();
		updateReadyButton();
		this.validate();
		this.repaint();
	}
	
	//--- MAIN UPDATE METHOD ---
	//Network thread calls this.
	public void update (Observable o, Object a) {
		final PhaseEnum e = currentRoundPhase(); 
		if (e != PhaseEnum.REGISTRATION)
			SwingUtilities.invokeLater(new Runnable(){public void run(){statusUpdate();}});
		else
			SwingUtilities.invokeLater(new Runnable(){public void run(){registerUpdate();}});
	}
	
	
	//Closes the bidAsk window
	private void closeBidAsk() {
		if (win != null) win.dispose();
		win = null;	
	}
	
	//This method opens up a new bidAsk window
	//if one already exists it closes that window first.
	private void bidAskWindow() {
		int i = Alist.getSelectedIndex();
		boolean isAsk = bidask.getText().contains("Ask");
		
		closeBidAsk();
		
		if (auctions == null || currentRoundPhase() != PhaseEnum.TRADING) return;

		win = (auctions == null || i > auctions.length || i < 0)  
				?	new SwingBidAsk(cgs.getPlayer().getName(),!isAsk,selectedCommodity(),m) 
				: 	new SwingBidAsk(cgs.getPlayer().getName(),!isAsk,auctions[i],m);
		win.setVisible(true);
		win.requestFocus();
	}
	
	//Sends the "Cancel Auction" packet to the server.
	private void sendCancel() {
		if (auctions == null) return;
		int i = Alist.getSelectedIndex();
		if (i < 0 || i >= auctions.length) return;
		Auction a = auctions[i];
		if (!isMyAuction(a)) return;
		
		m.send(new AuctionExpiration(a));
	}
	
	//Sends the Ready packet to the server 
	private void sendReady() {
		Player p = cgs.getPlayer();
		if (p == null) return;
		Sendable s = new ReadyPacket(p.getName(),!p.isReady());
		m.send(s);
	}
	
	//Determines whether an auction is the players auction.
	private boolean isMyAuction(Auction a) {
		if (myA == null) return false;
		for (int i = 0; i<myA.length; ++i)
			if (myA[i].equals(a)) return true;
		return false;
	}
	
	//This method closes this window and any children.
	public void closeAll() {
		if (win != null) win.dispose(); win = null;
	}
}
